package com.upb.webserver.developerappmap;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.HexUtil;
import com.upb.webserver.account.developer.DeveloperService;
import com.upb.webserver.application.ApplicationManager;
import com.upb.webserver.chain.ChainManager;
import com.upb.webserver.common.constant.Constant;
import com.upb.webserver.common.enums.PlatformEnum;
import com.upb.webserver.common.exception.WebServerException;
import com.upb.webserver.common.pojo.application.RspAppKeyVO;
import com.upb.webserver.common.returncode.ConstantCode;
import com.upb.webserver.common.tools.JsonUtils;
import com.upb.webserver.common.tools.MybatisExampleTools;
import com.upb.webserver.common.tools.WebServerTools;
import com.upb.webserver.common.tools.encryption.EccKeyUtil;
import com.upb.webserver.common.tools.encryption.P12Manager;
import com.upb.webserver.common.tools.encryption.SignUtil;
import com.upb.webserver.common.tools.encryption.sm2.SM2;
import com.upb.webserver.company.CompanyDeveloperService;
import com.upb.webserver.dao.entity.*;
import com.upb.webserver.dao.mapper.TbApplicationMapper;
import com.upb.webserver.dao.mapper.TbDeveloperAppMapMapper;
import com.upb.webserver.dao.mapper.TbDeveloperMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import sun.security.tools.keytool.CertAndKeyGen;
import sun.security.x509.X500Name;

import java.io.File;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.*;

import static com.upb.webserver.common.tools.encryption.SignUtil.verifyByEcPointHex;

@Slf4j
@Service
public class DeveloperAppMapService {
    @Autowired
    private TbApplicationMapper applicationMapper;
    @Autowired
    private DeveloperService developerService;
    @Autowired
    private ApplicationManager applicationManager;
    @Autowired
    private ChainManager chainManager;
    @Autowired
    private TbDeveloperMapper developerMapper;
    @Autowired
    private TbDeveloperAppMapMapper developerAppMapMapper;
    @Autowired
    private CompanyDeveloperService companyDeveloperService;


    @Transactional
    public void addByApplicationSetAndCompanySet(Set<Integer> applicationIdSet, Set<Integer> companyIdSet) {
        log.info("start exec method [addByApplicationSetAndCompanySet]. applicationIdSet:{} companyIdSet:{}", JsonUtils.objToString(applicationIdSet), JsonUtils.objToString(companyIdSet));
        List<Integer> developerIdList = companyDeveloperService.ListDeveloperIdByCompanyIdList(CollectionUtil.newArrayList(companyIdSet));
        ((DeveloperAppMapService) AopContext.currentProxy()).addByDeveloperSetAndApplicationSet(CollectionUtil.newHashSet(developerIdList), applicationIdSet);
        log.info("success exec method [addByApplicationSetAndCompanySet].");
    }


    @Transactional
    public void addByDeveloperSetAndCompanySet(Set<Integer> developerIdSet, Set<Integer> companyIdSet) {
        log.info("start exec method [addByDeveloperSetAndCompanySet]. developerIdSet:{} companyIdSet:{}", JsonUtils.objToString(developerIdSet), JsonUtils.objToString(companyIdSet));
        for (Integer companyId : companyIdSet) {
            Set<Integer> applicationIdSet = applicationManager.getApplicationIdSetByCompanyIdList(Arrays.asList(companyId));
            ((DeveloperAppMapService) AopContext.currentProxy()).addByDeveloperSetAndApplicationSet(developerIdSet, applicationIdSet);
        }
        log.info("success exec method [addMapOfDeveloperAndApplication].");
    }


    @Transactional
    public void addByDeveloperSetAndApplicationSet(Set<Integer> developerIdSet, Set<Integer> applicationIdSet) {
        log.info("start exec method [addByDeveloperSetAndApplicationSet]. developerIdSet:{} applicationIdSet:{}", JsonUtils.objToString(developerIdSet), JsonUtils.objToString(applicationIdSet));
        for (Integer developerId : developerIdSet) {
            applicationIdSet.stream().forEach(appId -> {
                ((DeveloperAppMapService) AopContext.currentProxy()).addMapOfDeveloperAndApplication(developerId, appId);
            });
        }
        log.info("success exec method [addByDeveloperSetAndApplicationSet].");
    }


    /**
     * @param developerId
     * @param applicationId
     */
    @Transactional
    public void addMapOfDeveloperAndApplication(int developerId, int applicationId) {
        log.info("start exec method [addMapOfDeveloperAndApplication]. developerId:{} applicationId:{}", developerId, applicationId);

        TbDeveloper developer = developerService.verifyDeveloperId(developerId);
        TbApplication application = applicationManager.verifyApplicationId(applicationId);
        TbChain tbChain = chainManager.verifyChainId(application.getChainId());

        if (CollectionUtil.isNotEmpty(developerAppMapMapper.selectByApplicationAndDeveloper(applicationId, developerId))) {
            log.info("found record by appId:{} and developerId:{}", applicationId, developerId);
            return;
        }

        SM2 sm2 = SM2.newInstance();
        String publicKeyBase64 = new String(Base64.getEncoder().encode(sm2.getKeyPair().getPrivateKey().toString().getBytes()), StandardCharsets.UTF_8);


        TbDeveloperAppMap tbDeveloperAppMap = new TbDeveloperAppMap();
        tbDeveloperAppMap.setDeveloperId(developerId);
        tbDeveloperAppMap.setSdkAppId(RandomStringUtils.randomAlphanumeric(20));
        tbDeveloperAppMap.setPrivateKey(sm2.getKeyPair().privateToHex());
        tbDeveloperAppMap.setPublicKeyBase64(publicKeyBase64);
        tbDeveloperAppMap.setPublicKeyEcPoint(sm2.getKeyPair().ecPointToHex());
        tbDeveloperAppMap.setDeveloperAccount(developer.getAccount());
        tbDeveloperAppMap.setApplicationId(applicationId);
        tbDeveloperAppMap.setApplicationFinalName(application.getFinalName());
        tbDeveloperAppMap.setChainId(application.getChainId());
        tbDeveloperAppMap.setChainType(application.getChainType());
        tbDeveloperAppMap.setEncryptionId(tbChain.getEncryptionId());
        tbDeveloperAppMap.setGmtCreate(new Date());
        tbDeveloperAppMap.setGmtModified(new Date());
        developerAppMapMapper.insertSelective(tbDeveloperAppMap);
        log.info("success exec method [addMapOfDeveloperAndApplication]. developerId:{} applicationId:{}", developerId, applicationId);
    }


    /**
     * @param platform
     * @param currentAccount
     * @return
     */
    public List<RspAppKeyVO> queryAppKeyList(byte platform, int currentAccount) {
        log.info("start exec method [queryAppKey]. platform:{} currentAccount:{}", platform, currentAccount);

        if (PlatformEnum.DEVELOPER.getId() != platform) {
            log.warn("fail exec method [queryAppKey]. just support platform:{} but input:{}", PlatformEnum.DEVELOPER.getId(), platform);
            return Collections.EMPTY_LIST;
        }
        TbDeveloperAppMapExample example = MybatisExampleTools.initSampleExample(TbDeveloperAppMapExample.class, new TbDeveloperAppMap(currentAccount));
        example.setOrderByClause(Constant.ORDER_BY_GMT_MODIFIED_DESC);

        List<TbDeveloperAppMap> list = developerAppMapMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(list)) return Collections.EMPTY_LIST;

        List<RspAppKeyVO> rspList = new ArrayList<>();
        for (TbDeveloperAppMap developerAppMap : list) {
            RspAppKeyVO rspVO = new RspAppKeyVO();
            BeanUtils.copyProperties(developerAppMap, rspVO);
            TbApplication application = applicationManager.verifyApplicationId(developerAppMap.getApplicationId());
            rspVO.setApplicationNameZh(application.getNameZh());
            rspVO.setApplicationName(application.getName());
            rspList.add(rspVO);
        }
        log.info("success exec method [queryAppKey]. platform:{} currentAccount:{}, result:{}");
        return rspList;
    }

    /**
     * @param keyId
     * @param passwordBase64
     * @return
     * @throws InvalidKeySpecException
     */
    public String getPKCS12OfPrivateKey(byte platform, int developerId, int keyId, char[] passwordBase64) throws InvalidKeySpecException {
        log.info("start exec method [getPKCS12OfPrivateKey]. platform:{} developerId:{} keyId:{}", platform, developerId, keyId);
        //query from db
        TbDeveloperAppMap tbDeveloperAppMap = developerAppMapMapper.selectByPrimaryKey(keyId);
        if (Objects.isNull(tbDeveloperAppMap)) {
            log.warn("fail exec method [getPKCS12OfPrivateKey]. not found tb_developer_app_map record by keyId:{}", keyId);
            throw new WebServerException(ConstantCode.INVALID_DEVELOPER_APP_MAP_ID);
        }
        if (PlatformEnum.DEVELOPER.getId() != platform || tbDeveloperAppMap.getDeveloperId() != developerId) {
            log.warn("fail exec method [getPKCS12OfPrivateKey]. found record by keyId:{} but not support platform:{} or developerId:{}", keyId, platform, developerId);
            throw new WebServerException(ConstantCode.NOT_SUPPORT_OPERATE);
        }

        //store
        BigInteger privateKeyBigInteger = HexUtil.toBigInteger(tbDeveloperAppMap.getPrivateKey());
        PrivateKey privateKey = EccKeyUtil.getPrivateKey(privateKeyBigInteger);
        char[] passwordWithCharArray = WebServerTools.bytesToChars(Base64.getDecoder().decode(WebServerTools.charsToBytes(passwordBase64)));
        String fileFullName = P12Manager.storeKeyAsP12File(privateKey, passwordWithCharArray);
        Arrays.fill(passwordWithCharArray, ' ');
        log.info("success exec method [getPKCS12OfPrivateKey]. keyId:{} result:{}", keyId, fileFullName);
        return fileFullName;
    }


    public void store(String publicKeyEcPoint, BigInteger privateKey, Key key, String password) {

        FileOutputStream fileOutputStream = null;

        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");

//  keyStore.load(new FileInputStream("output.p12"),"password".toCharArray());
            keyStore.load(null, null);


            CertAndKeyGen gen = new CertAndKeyGen("RSA", "SHA1WithRSA");
            gen.generate(1024);
//
//            Key key = gen.getPrivateKey();

            X509Certificate cert = gen.getSelfCertificate(new X500Name("CN=ROOT"), (long) 365 * 24 * 3600);

            X509Certificate[] chain = new X509Certificate[1];
            chain[0] = cert;

            keyStore.setKeyEntry("private", key, password.toCharArray(), null);
//            keyStore.setKeyEntry("private", key, password.toCharArray(), chain);

            File file = new File("./keyFile/private_key.p12");
            File parentFile = file.getParentFile();
            if (!parentFile.exists())
                parentFile.mkdirs();
            file.createNewFile();

            fileOutputStream = new FileOutputStream("./keyFile/private_key.p12");
            keyStore.store(fileOutputStream, password.toCharArray());


            String stringToSign = "dskf2329we23w23三ad发发是fksafas;dfkasdf;askdfjaskdjfaksjdfkasjf;ksjf;aksjfkasjdf;askfdjas;kdfj";
            // 从生成的 p12 文件读取私钥
            BCECPrivateKey loadPrivateKey = P12Manager.loadPrivateKey("./keyFile/private_key.p12", password);
            String loadPrivateKeyHexString = com.upb.webserver.common.tools.encryption.HexUtil.byteToHexUpper(loadPrivateKey.getD().toByteArray());
            System.out.println("Load private key from store file: " + loadPrivateKeyHexString);

            // 使用读取出来的私钥签名
            String signByPKHex = SignUtil.signByPKHex(loadPrivateKeyHexString, stringToSign.getBytes(StandardCharsets.UTF_8));

            // 使用公钥进行验证
            boolean isVerified = verifyByEcPointHex(publicKeyEcPoint, signByPKHex, stringToSign);
            System.out.println("Generate p12 private key file and verify by public key : " + isVerified);

            // 使用读取出来的私钥签名
//            String signByPKHex = SignUtil.signByPKHex(loadPrivateKeyHexString, stringToSign.getBytes(StandardCharsets.UTF_8));
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (Exception e) {
                    log.info("fail when try to close fileOutputStream", e);
                }
            }
        }
    }
}
